Web Service
===========

[Web service](http://pt.wikipedia.org/wiki/Web_service) é um sistema
de software projetado para suportar interações máquina-máquina
interoperáveis através de uma rede. No contexto de aplicações Web
geralmente refere-se a um conjunto de APIs que podem ser acessadas
através da Internet e executadas em um sistema remoto que hospeda
o serviço solicitado. Por exemplo, um cliente baseado em
[Flex](http://www.adobe.com/products/flex/), poderá chamar uma função
implementada no lado do servidor rodando uma aplicação Web baseada em
PHP. Web Service se baseia em [SOAP](http://pt.wikipedia.org/wiki/SOAP)
como a camada principal da pilha de protocolo de comunicação.

O Yii fornece as classes [CWebService] e [CWebServiceAction] para simplificar o trabalho
de implementação de Web Service em uma aplicação Web. As APIs são agrupadas
dentro de classes chamadas de *prestadores de serviço*. O Yii irá gerar para
cada classe uma especificação [WSDL](http://www.w3.org/TR/wsdl) que descreve
quais APIs estão disponíveis e como elas devem ser chamadas pelo cliente.
Quando uma API é chamada por um cliente, o Yii irá instanciar o prestador de
serviço correspondente e chamar a API requisitada para executar a requisição.

> Note|Nota: A classe [CWebService] é depende da [Extensão PHP
SOAP](http://www.php.net/manual/en/ref.soap.php). Tenha certeza que
você possui ela habilitada antes de testar os exemplos disponíveis
nesta seção.

Definindo um Prestador de Serviço
-------------------------

Como mencionado acima, o prestador de serviços é uma classe que define os
métodos que podem ser chamados remotamente. O Yii se baseia em [comentários
de documentação](http://java.sun.com/j2se/javadoc/writingdoccomments/) e
[reflexão de classes](http://php.net/manual/en/book.reflection.php) para
identificar quais métodos podem ser chamados remotamente e quais são os seus
parâmetros e valores retornados.

Vamos iniciar com um simples serviço de cotação de ações. Este serviço
permite uma requisição de um cliente para cotar uma ação específica.
Definimos o prestador de serviços como a seguir. Note que definimos a classe
prestadora de serviço `StockController` como extensão de [CController]. Isto
não é obrigatório. Explicaremos mais adiante por que fazemos dessa forma.

~~~
[php]
class StockController extends CController
{
	/**
	 * @param string símbolo da ação
	 * @return float preço da ação
	 * @soap
	 */
	public function getPrice($symbol)
	{
		$prices=array('IBM'=>100, 'GOOGLE'=>350);
		return isset($prices[$symbol])?$prices[$symbol]:0;
	    //...retorna o preço da ação para o $symbol
	}
}
~~~

Acima declaramos o método `getPrice` para ser a API do Web Service, marcando-o
com a tag `@soap` no comentário de documentação. Contamos com a documentação de
comentário para especificar o tipo de dados dos parâmetros de entrada e dos valores
de retorno. APIs adicionais podem ser declaradas de forma semelhante.


Declarando Ações do Web Service
----------------------------

Uma vez definido o prestador de serviço, precisamos fazer com que ele esteja
disponível para os clientes. Particularmente, precisamos criar uma ação de controle
para expor este serviço. Isto pode ser feito facilmente declarando uma ação
[CWebServiceAction] na classe de controle. Para exemplificar, colocamos ela na classe
`StockController`.

~~~
[php]
class StockController extends CController
{
	public function actions()
	{
		return array(
			'quote'=>array(
				'class'=>'CWebServiceAction',
			),
		);
	}

	/**
	 * @param string símbolo da ação
	 * @return float preço da ação
	 * @soap
	 */
	public function getPrice($symbol)
	{
	    //...retorna o preço da ação para o $symbol
	}
}
~~~

Isto é tudo o que precisamos para criar um Web Service!
Se tentar acessar a ação através da URL `http://hostname/path/to/index.php?r=stock/quote`,
vamos ver um monte de conteúdo XML que é o WSDL para o Web Service que definimos .

> Tip|Dica: Por padrão, [CWebServiceAction] assume que o controller corrente é
o prestador de serviço. É por isso que definimos o método `getPrice` dentro
da classe `StockController`.

Utilizando um Web Service
---------------------

Para completar o exemplo, vamos criar um cliente que utiliza o Web Service
que acabamos de criar. O cliente de exemplo é escrito em PHP, porém, ele
poderia ser em outra linguagem como `Java`, `C#`, `Flex`, etc.

~~~
[php]
$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote');
echo $client->getPrice('GOOGLE');
~~~

Executando o script acima em modo Web ou console e veremos que `350` é o preço
para `GOOGLE`.

Tipos de dados
----------

Quando declaramos métodos e propriedades de classes que são acessadas remotamente,
precisamos especificar o tipo de dados dos parâmetros de entrada e saída. A seguir,
os tipos de dados mais primitivos que podemos utilizar:

   - str/string: mapeado para `xsd:string`;
   - int/integer: mapeado para `xsd:int`;
   - float/double: mapeado para `xsd:float`;
   - bool/boolean: mapeado para `xsd:boolean`;
   - date: mapeado para `xsd:date`;
   - time: mapeado para `xsd:time`;
   - datetime: mapeado para `xsd:dateTime`;
   - array: mapeado para `xsd:string`;
   - object: mapeado para `xsd:struct`;
   - mixed: mapeado para `xsd:anyType`.


Se um tipo não é um dos descritos acima, ele será considerado como um
tipo composto que consiste de propriedades. Um tipo composto é representado
em termos de uma classe e suas propriedades como variáveis públicas da classe,
marcadas com a tag `@soap` em seu comentário de documentação.

Podemos utilizar também o tipo array adicionando `[]` no final do tipo primitivo
ou composto. Isto poderia especificar um array de um tipo específico.

Abaixo um exemplo que define a Web API `getPosts` que retorna um array de
objetos `Post`.

~~~
[php]
class PostController extends CController
{
	/**
	 * @return Post[] uma lista de Post
	 * @soap
	 */
	public function getPosts()
	{
		return Post::model()->findAll();
	}
}

class Post extends CActiveRecord
{
	/**
	 * @var integer ID do post
	 * @soap
	 */
	public $id;
	/**
	 * @var string título do post
	 * @soap
	 */
	public $title;

	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}
}
~~~

Mapeamento de Classes
-------------

Para receber parâmetros de tipo composto de um cliente, um
aplicativo precisa declarar o mapeamento de tipos WSDL para as
classes PHP correspondentes. Isto é realizado configurando a propriedade
[classMap|CWebServiceAction::classMap] de [CWebServiceAction].

~~~
[php]
class PostController extends CController
{
	public function actions()
	{
		return array(
			'service'=>array(
				'class'=>'CWebServiceAction',
				'classMap'=>array(
					'Post'=>'Post',  // ou simplesmente 'Post'
				),
			),
		);
	}
	......
}
~~~

Interceptando Chamadas a Método Remoto
-------------------------------------

Com a implementação da interface [IWebServiceProvider], um prestador de serviço
pode interceptar chamadas a método remoto. Em [IWebServiceProvider::beforeWebMethod],
o prestador de serviços pode recuperar o estado atual da instância de [CWebService]
e obter o nome do método que está sendo chamado atualmente através de
[CWebService::methodName]. Ele pode retornar false se o método remoto não pode ser
chamado por algum motivo (por exemplo, acesso não autorizado).

<div class="revision">$Id: topics.webservice.txt 1808 2010-02-17 21:49:42Z qiang.xue $</div>
